-
Notifications
You must be signed in to change notification settings - Fork 60
Add data_ptr
method to Vips::Image
#418
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
base: master
Are you sure you want to change the base?
Conversation
Hi @ankane, thanks for this! Unfortunately, https://www.libvips.org/API/current/libvips-header.html#vips-image-get-data The threadsafe equivalent is https://libvips.github.io/ruby-vips/Vips/Image.html#copy_memory-instance_method https://www.libvips.org/API/current/VipsImage.html#vips-image-copy-memory |
Thanks @jcupitt. It sounds like the thread-safe way to get a read-only pointer with minimal copying is to combine the two. copy = image.copy_memory
read_ptr = copy.data_ptr What do you think about having a |
Yes, that's better. Having What happens if the caller slices the result from |
Sounds good. Calling |
Great! Please add a note to the changelog (and credit yourself!), and a note to the docs saying this is potentially a very expensive operation. Thank you for doing this work! |
Thanks @jcupitt. Re expensive: I feel like I'm missing something about how libvips works that may make this PR not very useful. Both image = Vips::Image.new_from_file("image.png")
ptr = image.read_ptr
p image.write_to_memory == ptr.read_string(ptr.size) # => true However, require "benchmark/ips"
require "ruby-vips"
Benchmark.ips do |x|
x.report("write_to_memory") do
image = Vips::Image.new_from_file("image.png")
image.write_to_memory
end
x.report("read_ptr") do
image = Vips::Image.new_from_file("image.png")
image.read_ptr
end
end Output
I'd expect them to be roughly the same when the image pixels aren't already loaded (since they produce the same result), and I'd expect |
You're right, I'd expect |
I made a small C prog for testing: // compile with
// gcc -g -Wall copywrite.c `pkg-config vips --cflags --libs`
#include <vips/vips.h>
int
main(int argc, char **argv)
{
VIPS_INIT(argv[0]);
// 2.33s, 75mb
for (int i = 0; i < 1000; i++) {
VipsImage *image = vips_image_new_from_file(argv[1], NULL);
size_t size = VIPS_IMAGE_SIZEOF_IMAGE(image);
void *buf = g_try_malloc(size);
VipsImage *x = vips_image_new_from_memory(buf, size,
image->Xsize, image->Ysize, image->Bands, image->BandFmt);
vips_image_write(image, x);
g_object_unref(x);
g_free(buf);
g_object_unref(image);
}
// 2.93s, 75mb
for (int i = 0; i < 1000; i++) {
VipsImage *image = vips_image_new_from_file(argv[1], NULL);
VipsImage *x = vips_image_new_memory();
vips_image_write(image, x);
g_object_unref(x);
g_object_unref(image);
}
// 2.33s, 75mb
for (int i = 0; i < 1000; i++) {
VipsImage *image = vips_image_new_from_file(argv[1], NULL);
void *buf = vips_image_write_to_memory(image, NULL);
g_free(buf);
g_object_unref(image);
}
return 0;
} The middle one is what Anyway, the difference is relatively small, and not the huge difference you are seeing, which means it must be an inefficiency in ruby-vips. The same program in ruby-vips is: #!/usr/bin/env ruby
require "ruby-vips"
1000.times do |i|
puts "loop #{i} ..."
image = Vips::Image.new_from_file(ARGV[0])
# 4.0s, 200mb
#image.write_to_memory
# 18.9s, 2.1gb
image.copy_memory
end So Looking at the code, it's just: def copy_memory
new_image = Vips.vips_image_copy_memory self
Vips::Image.new new_image
end Which (I think?) should be OK. I'll keep digging. |
Changing def copy_memory
new_image = Vips.vips_image_copy_memory self
::GObject.g_object_unref new_image
end Obviously won't work, haha, but it gets the time down to 3.2s. If I make it: def copy_memory
Vips::Image.new_from_memory self.write_to_memory,
self.width, self.height, self.bands, self.format
end I see 4.0s, 240mb, so the same as I'm not sure why |
Sampling the following program on Mac arm64: require "ruby-vips"
loop do
image = Vips::Image.new_from_file("image.png")
image.copy_memory
end shows it's spending most of the time (~95%) on
|
That's very strange. I'll try sysprof here. |
Hi, this PR adds support for vips_image_get_data to Ruby with a new
data_ptr
method that returns a sizedFFI::Pointer
. This provides similar functionality aswrite_to_memory
but without additional copying.With the current implementation, it would be the user's responsibility to ensure the pointer has not been invalidated (but we could add a reference to the pointer to ensure the image is not garbage collected while the pointer is active - I'm not sure if there are other operations that invalidate the pointer).
Ref: FFI enum docs